package com.iwhalecloud.citybrain.flow.platform.manager.application.utils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.iwhalecloud.citybrain.flow.platform.manager.application.utils.FlowGraphTraveller.VisitorParam;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.exception.TaskRollbackException;
import lombok.Data;
import org.apache.http.util.Asserts;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.InclusiveGateway;
import org.flowable.bpmn.model.ParallelGateway;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.ProcessInstance;

/**
 * 验证当前节点到目标节点是否可以回退
 * @author lich
 * @date 2021/11/9
 */
public class RollbackValidator {

    @Data
    public static class ValidateResult {

        private boolean success;

        private String message;

        private boolean jumpToFirst;

        private boolean crossGateway;

    }

    private RepositoryService repositoryService;

    private RuntimeService runtimeService;

    private String currentActivityId;

    private String processInstanceId;

    private List<String> firstUserTaskIds;

    private List<String> targetActivityIds;

    private ProcessInstance processInstance;

    private List<String> targetNames = new ArrayList<>();

    private Set<String> taskIdInRange = new HashSet<>();

    private FlowGraphTraveller flowTraveller;

    public RollbackValidator(String processInstanceId, String currentActivityId, List<String> targetActivityIds) {
        this.processInstanceId = processInstanceId;
        this.targetActivityIds = Lists.newArrayList(targetActivityIds);
        this.currentActivityId = currentActivityId;
    }

    public void executeValidation() {
        Asserts.notNull(runtimeService, "runtimeService 未设置");
        this.processInstance =
            runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).active().singleResult();

        checkCurrentActivityIdValid();
        traverseFromCurrentActivityIdToTarget();
    }

    private void checkCurrentActivityIdValid() {
        if (targetActivityIds.contains(currentActivityId)) {
            throw new TaskRollbackException("不能跳转到当前节点");
        }
    }

    private void traverseFromCurrentActivityIdToTarget() {
        Asserts.notNull(runtimeService, "runtimeService 未设置");
        Asserts.notNull(repositoryService, "repositoryService 未设置");
        Asserts.notNull(processInstance , "找不到流程实例：" + processInstanceId + " 对应的流程模型");

        Process mainProcess = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()).getMainProcess();
        for (String targetAId : targetActivityIds) {
            FlowElement flowElement = mainProcess.getFlowElement(targetAId);
            if (flowElement != null) {
                targetNames.add(flowElement.getName());
            }
        }
        if (flowTraveller == null) {
            flowTraveller = new FlowGraphTraveller(mainProcess);
        }
        List<UserTask> firstTasks = flowTraveller.findFirstUserTasks();
        this.firstUserTaskIds = firstTasks.stream().map(UserTask::getId).collect(Collectors.toList());
        flowTraveller.traverseBackward(this.currentActivityId, this::onVisitUserTask, this::onMeetGateway);
    }

    public ValidateResult provideValidateResult() {
        ValidateResult vr = new ValidateResult();
        boolean jumpToFirst = Sets.symmetricDifference(Sets.newHashSet(firstUserTaskIds),
            Sets.newHashSet(targetActivityIds)).isEmpty();
        vr.setJumpToFirst(jumpToFirst);
        vr.setSuccess(true);
        vr.setCrossGateway(!taskIdInRange.containsAll(targetActivityIds));
        return vr;
    }

    private void onVisitUserTask(VisitorParam param) {
        if (param.getUserTask() != null) {
            taskIdInRange.add(param.getUserTask().getId());
        }
    }

    private boolean onMeetGateway(VisitorParam param) {
        FlowElement flowElement = param.getFlowElement();
        return (flowElement instanceof ParallelGateway || flowElement instanceof InclusiveGateway);
    }

    public void setRepositoryService(RepositoryService repositoryService) {
        this.repositoryService = repositoryService;
    }

    public void setRuntimeService(RuntimeService runtimeService) {
        this.runtimeService = runtimeService;
    }

    public ProcessInstance getProcessInstance() {
        return processInstance;
    }

    public String getTargetNameStr() {
        return String.join(",", this.targetNames);
    }

    public String getTaskIdInRange() {
        return String.join(",", this.taskIdInRange);
    }
}
